在Unity中,烘焙LightMap采用的是一个场景烘焙一组LightMap。而对于大世界场景来说,没办法把世界上所有的物体在同一场景下烘焙。Unity提供的解决办法是通过SubScene来解决,就是分场景烘焙,然后再通过加载卸载Scene的方式来实现。但有时候有这样的需求,同一组室内场景可能在多个地方存在,美术希望烘焙好一组物体,能复制到各个地方,并且能很好地预览,这样使用SubScene来说就比较麻烦了。
先说一下 unity的LightMap机制,烘焙分为动态物体和静态物体,动态物体走的是GI,通过环境光,LightProb等这些算出三维光照数据,然后计算动态物体的球谐光照。对于静态物体来说就会烘焙成 lightmap,一般有几组三张贴图(color,dir以及shadow)。静态物体的MeshRender上会有个lightmapIndex存放采用第几组lightmap,还有个lightmapScaleOffset存放uv偏移,通过这两个数据就能显示正确。
知道LightMap的原理后就比较简单了,我们只需要存好我们需要使用的数据,然后设置对应的位置就能正确显示了。
首先,我们定义好我们的数据结构,我们期望在一个prefab上挂一个我们的脚本,然后加载这个prefab上所有的MeshRender。我们就需要一个这样的ScriptObject。
- public class CustomLightMapDataMap : ScriptableObject
- {
- public MeshLightmapData[] LightMapDatas = null;
- }
- [Serializable]
- public struct CustomLightmapData
- {
- /// <summary>
- /// The color for lightmap.
- /// </summary>
- public Texture2D LightmapColor;
-
- /// <summary>
- /// The dir for lightmap.
- /// </summary>
- public Texture2D LightmapDir;
-
- /// <summary>
- /// The shadowmask for lightmap.
- /// </summary>
- public Texture2D ShadowMask;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CustomLightmapData"/> struct.
- /// </summary>
- /// <param name="data">lightmapdata.</param>
- public CustomLightmapData(LightmapData data)
- {
- this.LightmapColor = data.lightmapColor;
- this.LightmapDir = data.lightmapDir;
- this.ShadowMask = data.shadowMask;
- }
-
- public bool IsA(LightmapData data)
- {
- return this.LightmapColor == data.lightmapColor &&
- this.LightmapDir == data.lightmapDir &&
- this.ShadowMask == data.shadowMask;
- }
-
- public LightmapData GetLightmapData()
- {
- LightmapData data = new LightmapData();
- data.lightmapColor = this.LightmapColor;
- data.lightmapDir = this.LightmapDir;
- data.shadowMask = this.ShadowMask;
- return data;
- }
- }
-
- [Serializable]
- public struct MeshLightmapData
- {
- public Vector4 LightmapScaleOffset;
-
- public CustomLightmapData LightmapData;
- }
然后再在编辑器上弄一个菜单,选中物体就能自动干这件事情。
- [MenuItem("Window/LightMapGenerate")]
- private static void Generated()
- {
- string outputPath = "Assets/LightMapPrefab";
- var lightmapPath = GetLightMapPath();
- if (!string.IsNullOrEmpty(lightmapPath))
- {
- outputPath = Path.GetDirectoryName(lightmapPath);
- }
-
- GameObject obj = Selection.activeGameObject;
- if (obj == null)
- {
- return;
- }
-
- var dataMap = (CustomLightMapDataMap)ScriptableObject.CreateInstance(typeof(CustomLightMapDataMap));
- var renders = obj.GetComponentsInChildren<MeshRenderer>();
- List<MeshLightmapData> datas = new List<MeshLightmapData>();
- var lightmaps = LightmapSettings.lightmaps;
- foreach (var render in renders)
- {
- if (render.lightmapIndex < 0 || render.lightmapIndex >= lightmaps.Length)
- {
- Debug.LogError("lightmap error:" + render.gameObject.name);
- return;
- }
- var data = new MeshLightmapData()
- {
- LightmapScaleOffset = render.lightmapScaleOffset,
- LightmapData = new CustomLightmapData(lightmaps[render.lightmapIndex]),
- };
- datas.Add(data);
- }
-
- dataMap.LightMapDatas = datas.ToArray();
- var loader = obj.GetComponent<LightMapDataLoader>();
- if (loader == null)
- {
- loader = obj.AddComponent<LightMapDataLoader>();
- }
-
- outputPath = Path.Combine(outputPath, obj.name + ".asset");
- AssetDatabase.CreateAsset(dataMap, outputPath);
- AssetDatabase.SaveAssets();
- loader.Asset = AssetDatabase.LoadAssetAtPath<CustomLightMapDataMap>(outputPath);
- }
-
- private static string GetLightMapPath()
- {
- var lightmaps = LightmapSettings.lightmaps;
- if (lightmaps.Length == 0)
- {
- return string.Empty;
- }
-
- return AssetDatabase.GetAssetPath(lightmaps[0].lightmapColor);
- }
数据保存好了,我们只需要加载就好了。加载除了要加载MeshRender上的数据,还要设置好场景的LightMap。这里还有个特别重要的问题就是卸载,在物体销毁时,我们要处理场景的lightmap,这里需要通过一个计数器去干这件事情,当引用计数为0了,我们就去清理lightmap贴图数据。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.SceneManagement;
-
- [ExecuteInEditMode]
- public class LightMapDataLoader : MonoBehaviour
- {
- private static Dictionary<CustomLightmapData, int> lightmapDataRefenceCount = new Dictionary<CustomLightmapData, int>();
-
- [SerializeField]
- private CustomLightMapDataMap asset;
-
- private HashSet<CustomLightmapData> lightmapDatas = new HashSet<CustomLightmapData>();
-
- public CustomLightMapDataMap Asset
- {
- get { return this.asset; }
- set { this.asset = value; }
- }
-
- public static void Clear()
- {
- lightmapDataRefenceCount.Clear();
- }
-
- // Start is called before the first frame update
- private void Awake()
- {
- if (this.asset != null)
- {
- var lightmaps = LightmapSettings.lightmaps;
- var renders = this.GetComponentsInChildren<MeshRenderer>();
- var datas = this.asset.LightMapDatas;
- if (datas.Length != renders.Length)
- {
- return;
- }
-
- List<LightmapData> lightmapList = new List<LightmapData>(lightmaps);
- for (int i = 0; i < datas.Length; i++)
- {
- var lightMapIndex = -1;
- var nullIndex = -1;
- LightmapData currentData = null;
- for (int j = lightmapList.Count - 1; j >= 0; j--)
- {
- var lightmap = lightmapList[j];
- if (datas[i].LightmapData.IsA(lightmap))
- {
- lightMapIndex = j;
- currentData = lightmap;
- }
-
- if (lightmap.lightmapColor == null &&
- lightmap.lightmapDir == null &&
- lightmap.shadowMask == null)
- {
- nullIndex = j;
- }
- }
-
- if (lightMapIndex == -1)
- {
- currentData = datas[i].LightmapData.GetLightmapData();
- if (nullIndex == -1)
- {
- lightmapList.Add(currentData);
- lightMapIndex = lightmapList.Count - 1;
- }
- else
- {
- lightmapList[nullIndex] = currentData;
- lightMapIndex = nullIndex;
- }
- }
-
- this.lightmapDatas.Add(datas[i].LightmapData);
- renders[i].lightmapIndex = lightMapIndex;
- renders[i].lightmapScaleOffset = datas[i].LightmapScaleOffset;
- }
-
- foreach (var data in this.lightmapDatas)
- {
- if (!lightmapDataRefenceCount.TryGetValue(data, out var count))
- {
- count = 0;
- }
- else
- {
- lightmapDataRefenceCount.Remove(data);
- }
-
- count++;
- lightmapDataRefenceCount.Add(data, count);
- }
-
- LightmapSettings.lightmaps = lightmapList.ToArray();
- }
- }
-
- private void OnDestroy()
- {
- foreach (var data in this.lightmapDatas)
- {
- if (lightmapDataRefenceCount.TryGetValue(data, out var count))
- {
- count--;
- lightmapDataRefenceCount.Remove(data);
- if (count == 0)
- {
- var lightmaps = LightmapSettings.lightmaps;
- for (int i = 0; i < lightmaps.Length; i++)
- {
- if (data.IsA(lightmaps[i]))
- {
- lightmaps[i].lightmapColor = null;
- lightmaps[i].lightmapDir = null;
- lightmaps[i].shadowMask = null;
- }
- }
-
- LightmapSettings.lightmaps = lightmaps;
- }
- else
- {
- lightmapDataRefenceCount.Add(data, count);
- }
- }
- }
- }
- }
做好这些事情之后,我们就可以在场景中烘焙一组物体,然后选中Root,点击Window/LightMapGenerate,会帮你组织好数据,挂好脚本。你可以把这个物体复制到任何地方都是显示正确,也可以保存成prefab通过程序加载和销毁。


21 天前回复
举报

474




